/*
 * Copyright (C) 2001-2011 Jacek Sieka, arnetheduck on gmail point com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "stdinc.h"
#include "ConnectivityManager.h"

#include "ClientManager.h"
#include "ConnectionManager.h"
#include "LogManager.h"
#include "MappingManager.h"
#include "SearchManager.h"
#include "SettingsManager.h"

#include "../dht/dht.h"

ConnectivityManager::ConnectivityManager() :
	autoDetected(false),
	running(false)
{
}

void ConnectivityManager::startSocket()
{
	autoDetected = false;
	
	disconnect();
	
	// active check mustn't be there to hub-dependent setting works correctly
	//if(ClientManager::getInstance()->isActive()) {
	listen();
#ifdef PPA_INCLUDE_UPNP
	// must be done after listen calls; otherwise ports won't be set
	if (SETTING(INCOMING_CONNECTIONS) == SettingsManager::INCOMING_FIREWALL_UPNP)
		MappingManager::getInstance()->open();
#endif
	//}
}

void ConnectivityManager::detectConnection()
{
	if (running)
		return;
	running = true;
	
	status.clear();
	fire(ConnectivityManagerListener::Started());

	// restore connectivity settings to their default value.
	SettingsManager::getInstance()->unset(SettingsManager::TCP_PORT);
	SettingsManager::getInstance()->unset(SettingsManager::UDP_PORT);
	SettingsManager::getInstance()->unset(SettingsManager::TLS_PORT);
	SettingsManager::getInstance()->unset(SettingsManager::DHT_PORT);
	SettingsManager::getInstance()->unset(SettingsManager::EXTERNAL_IP);
	SettingsManager::getInstance()->unset(SettingsManager::NO_IP_OVERRIDE);
	SettingsManager::getInstance()->unset(SettingsManager::BIND_ADDRESS);

	if (MappingManager::getInstance()->getOpened()) {
		MappingManager::getInstance()->close();
	}
	
	disconnect();
	
	log(STRING(DETERMIN_CON_TYPE));
	try
	{
		listen();
	}
	catch (const Exception& e)
	{
		SET_SETTING(INCOMING_CONNECTIONS, SettingsManager::INCOMING_FIREWALL_PASSIVE);
		SET_SETTING(ALLOW_NAT_TRAVERSAL, true);
		AutoArray<char> buf(512);
		snprintf(buf.get(), 512, CSTRING(UNABLE_TO_OPEN_PORT), e.getError().c_str());
		log(buf.get());
		fire(ConnectivityManagerListener::Finished());
		running = false;
		return;
	}
	
	autoDetected = true;
	
	if (!Util::isPrivateIp(Util::getLocalIp()))
	{
		SET_SETTING(INCOMING_CONNECTIONS, SettingsManager::INCOMING_DIRECT);
		log(STRING(PUBLIC_IP_DETECTED));
		fire(ConnectivityManagerListener::Finished());
		running = false;
		return;
	}
#ifdef PPA_INCLUDE_UPNP
	SET_SETTING(INCOMING_CONNECTIONS, SettingsManager::INCOMING_FIREWALL_UPNP);
	log(STRING(LOCAL_NET_NAT_DETECT));
	
	if (!MappingManager::getInstance()->open())
	{
		running = false;
	}
#endif
}

void ConnectivityManager::setup(bool settingsChanged)
{
	if (BOOLSETTING(AUTO_DETECT_CONNECTION))
	{
		if (!autoDetected) detectConnection();
	}
	else
	{
		if (autoDetected || settingsChanged)
		{
#ifdef PPA_INCLUDE_UPNP
			if (settingsChanged || SETTING(INCOMING_CONNECTIONS) != SettingsManager::INCOMING_FIREWALL_UPNP)
			{
				MappingManager::getInstance()->close();
			}
#endif
			startSocket();
		}
#ifdef PPA_INCLUDE_UPNP
		else if (SETTING(INCOMING_CONNECTIONS) == SettingsManager::INCOMING_FIREWALL_UPNP && !MappingManager::getInstance()->getOpened())
		{
			// previous mappings had failed; try again
			MappingManager::getInstance()->open();
		}
#endif
	}
}

void ConnectivityManager::mappingFinished(const string& mapper)
{
	if (BOOLSETTING(AUTO_DETECT_CONNECTION))
	{
		if (mapper.empty())
		{
			//StrongDC++: don't disconnect when mapping fails else DHT and active mode in favorite hubs won't work
			//disconnect();
			SET_SETTING(INCOMING_CONNECTIONS, SettingsManager::INCOMING_FIREWALL_PASSIVE);
			SET_SETTING(ALLOW_NAT_TRAVERSAL, true);
			log(STRING(AUTOMATIC_SETUP_ACTIV_MODE_FAILED));
		}
		else
		{
			SET_SETTING(MAPPER, mapper);
		}
		fire(ConnectivityManagerListener::Finished());
	}
	
	running = false;
}

void ConnectivityManager::listen()
{
	try
	{
		ConnectionManager::getInstance()->listen();
	}
	catch (const Exception&)
	{
		throw Exception("TCP/TLS");
	}
	
	try
	{
		SearchManager::getInstance()->listen();
	}
	catch (const Exception&)
	{
		throw Exception("UDP");
	}
	
	try
	{
	   if(dht::DHT::getInstance()->isValidInstance()) //[+]PPA
		  dht::DHT::getInstance()->start();
	}
	catch (const Exception&)
	{
		throw Exception("DHT");
	}
}

void ConnectivityManager::disconnect()
{
	SearchManager::getInstance()->disconnect();
	ConnectionManager::getInstance()->disconnect();
	if(dht::DHT::getInstance()->isValidInstance()) //[+]PPA
	   dht::DHT::getInstance()->stop();
}

void ConnectivityManager::log(const string& message)
{
	if (BOOLSETTING(AUTO_DETECT_CONNECTION))
	{
		status = move(message);
		LogManager::getInstance()->message(STRING(CONNECTIVITY) + status);
		fire(ConnectivityManagerListener::Message(), status);
	}
	else
	{
		LogManager::getInstance()->message(message);
	}
}